﻿using System;
using System.Text.RegularExpressions;

namespace AssemblerCompiler
{
    internal class Function
    {
        private readonly UInt16 address;
        private readonly int byteIndToWriteMemory;
        private readonly int byteToWriteD16;
        private readonly int byteToWriteD8;
        private readonly string cmdName;
        private readonly Byte[] code;
        private readonly bool needWriteD16;
        private readonly bool needWriteD8;
        private readonly bool needWriteMemory;
        private readonly int numOp;
        private readonly Operand op1;
        private readonly Operand op2;
        private string memoryName;
        private SymTabElement.SymType memoryType;

        public Function(string cmd, UInt16 address)
        {
            this.address = address;

            var cmdZeroOperands = new Regex(@"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s*$", RegexOptions.IgnoreCase);
            var cmdOneOperand = new Regex(@"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s+(?<op1>[_A-Z0-9\[\]+][_A-Z0-9\[\]\+\s]*)$",
                                          RegexOptions.IgnoreCase);
            var cmdTwoOperands =
                new Regex(
                    @"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s+(?<op1>[_A-Z0-9\[\]\+][_A-Z0-9\[\]\+\s]*),\s*(?<op2>[_A-Z0-9\[\]\+][_A-Z0-9\[\]\+\s]*)$",
                    RegexOptions.IgnoreCase);

            #region ZERO OPERANDS

            Match m = cmdZeroOperands.Match(cmd);
            if (m.Success)
            {
                this.numOp = 0;
                this.cmdName = m.Groups["name"].Value.ToUpper();
                if (this.cmdName == "NOP")
                {
                    this.code = ZeroOperandsTranslator.ParseNOP(ref this.needWriteMemory, ref this.byteIndToWriteMemory,
                                                                ref this.needWriteD8, ref this.byteToWriteD8,
                                                                ref this.needWriteD16, ref this.byteToWriteD16);
                }
                else if (this.cmdName == "PUSHF")
                {
                    this.code = ZeroOperandsTranslator.ParsePUSHF(ref this.needWriteMemory,
                                                                  ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                                  ref this.byteToWriteD8, ref this.needWriteD16,
                                                                  ref this.byteToWriteD16);
                }
                else if (this.cmdName == "POPF")
                {
                    this.code = ZeroOperandsTranslator.ParsePOPF(ref this.needWriteMemory, ref this.byteIndToWriteMemory,
                                                                 ref this.needWriteD8, ref this.byteToWriteD8,
                                                                 ref this.needWriteD16, ref this.byteToWriteD16);
                }
                else if (this.cmdName == "RET")
                {
                    this.code = ZeroOperandsTranslator.ParseRET(ref this.needWriteMemory, ref this.byteIndToWriteMemory,
                                                                ref this.needWriteD8, ref this.byteToWriteD8,
                                                                ref this.needWriteD16, ref this.byteToWriteD16);
                }
                else if (this.cmdName == "CBW")
                {
                    this.code = ZeroOperandsTranslator.ParseCBW(ref this.needWriteMemory, ref this.byteIndToWriteMemory,
                                                                ref this.needWriteD8, ref this.byteToWriteD8,
                                                                ref this.needWriteD16, ref this.byteToWriteD16);
                }
                else if (this.cmdName == "CWD")
                {
                    this.code = ZeroOperandsTranslator.ParseCWD(ref this.needWriteMemory, ref this.byteIndToWriteMemory,
                                                                ref this.needWriteD8, ref this.byteToWriteD8,
                                                                ref this.needWriteD16, ref this.byteToWriteD16);
                }
                else
                {
                    throw new Exception("Unknown command!");
                }

                return;
            }

            #endregion

            #region ONE OPERAND

            m = cmdOneOperand.Match(cmd);
            if (m.Success)
            {
                this.numOp = 1;
                this.op1 = new Operand(m.Groups["op1"].Value);
                this.cmdName = m.Groups["name"].Value.ToUpper();
                if (this.cmdName == "LOOP")
                {
                    this.code = OneOperandTranslator.ParseLOOP(this.op1, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Label;
                }
                else if (this.cmdName == "LOOPE")
                {
                    this.code = OneOperandTranslator.ParseLOOPE(this.op1, ref this.needWriteMemory,
                                                                ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                                ref this.byteToWriteD8, ref this.needWriteD16,
                                                                ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Label;
                }
                else if (this.cmdName == "INT")
                {
                    this.code = OneOperandTranslator.ParseINT(this.op1, ref this.needWriteMemory,
                                                              ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                              ref this.byteToWriteD8, ref this.needWriteD16,
                                                              ref this.byteToWriteD16);
                }
                else if (this.cmdName == "PUSH")
                {
                    this.code = OneOperandTranslator.ParsePUSH(this.op1, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "POP")
                {
                    this.code = OneOperandTranslator.ParsePOP(this.op1, ref this.needWriteMemory,
                                                              ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                              ref this.byteToWriteD8, ref this.needWriteD16,
                                                              ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "JE")
                {
                    this.code = OneOperandTranslator.ParseJE(this.op1, ref this.needWriteMemory,
                                                             ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                             ref this.byteToWriteD8, ref this.needWriteD16,
                                                             ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Label;
                }
                else if (this.cmdName == "JG")
                {
                    this.code = OneOperandTranslator.ParseJG(this.op1, ref this.needWriteMemory,
                                                             ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                             ref this.byteToWriteD8, ref this.needWriteD16,
                                                             ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Label;
                }
                else if (this.cmdName == "JL")
                {
                    this.code = OneOperandTranslator.ParseJL(this.op1, ref this.needWriteMemory,
                                                             ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                             ref this.byteToWriteD8, ref this.needWriteD16,
                                                             ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Label;
                }
                else if (this.cmdName == "JNE")
                {
                    this.code = OneOperandTranslator.ParseJNE(this.op1, ref this.needWriteMemory,
                                                              ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                              ref this.byteToWriteD8, ref this.needWriteD16,
                                                              ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Label;
                }
                else if (this.cmdName == "JMP")
                {
                    this.code = OneOperandTranslator.ParseJMP(this.op1, ref this.needWriteMemory,
                                                              ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                              ref this.byteToWriteD8, ref this.needWriteD16,
                                                              ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Label;
                }
                else if (this.cmdName == "CALL")
                {
                    this.code = OneOperandTranslator.ParseCALL(this.op1, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Procedure;
                }
                else if (this.cmdName == "RET")
                {
                    this.code = OneOperandTranslator.ParseRET(this.op1, ref this.needWriteMemory,
                                                              ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                              ref this.byteToWriteD8, ref this.needWriteD16,
                                                              ref this.byteToWriteD16);
                }
                else if (this.cmdName == "NOT")
                {
                    this.code = OneOperandTranslator.ParseNOT(this.op1, ref this.needWriteMemory,
                                                              ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                              ref this.byteToWriteD8, ref this.needWriteD16,
                                                              ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "INC")
                {
                    this.code = OneOperandTranslator.ParseINC(this.op1, ref this.needWriteMemory,
                                                              ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                              ref this.byteToWriteD8, ref this.needWriteD16,
                                                              ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "DEC")
                {
                    this.code = OneOperandTranslator.ParseDEC(this.op1, ref this.needWriteMemory,
                                                              ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                              ref this.byteToWriteD8, ref this.needWriteD16,
                                                              ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "IDIV")
                {
                    this.code = OneOperandTranslator.ParseIDIV(this.op1, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                }
                else
                {
                    throw new Exception("Unknown command!");
                }

                return;
            }

            #endregion

            #region TWO OPERANDS

            m = cmdTwoOperands.Match(cmd);
            if (m.Success)
            {
                this.numOp = 2;
                this.op1 = new Operand(m.Groups["op1"].Value);
                this.op2 = new Operand(m.Groups["op2"].Value);
                this.cmdName = m.Groups["name"].Value.ToUpper();
                if (this.cmdName == "MOV")
                {
                    this.code = TwoOperandsTranslator.ParseMOV(this.op1, this.op2, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "LEA")
                {
                    this.code = TwoOperandsTranslator.ParseLEA(this.op1, this.op2, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "ADD")
                {
                    this.code = TwoOperandsTranslator.ParseADD(this.op1, this.op2, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "SUB")
                {
                    this.code = TwoOperandsTranslator.ParseSUB(this.op1, this.op2, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "AND")
                {
                    this.code = TwoOperandsTranslator.ParseAND(this.op1, this.op2, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "OR")
                {
                    this.code = TwoOperandsTranslator.ParseOR(this.op1, this.op2, ref this.needWriteMemory,
                                                              ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                              ref this.byteToWriteD8, ref this.needWriteD16,
                                                              ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "XOR")
                {
                    this.code = TwoOperandsTranslator.ParseXOR(this.op1, this.op2, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "CMP")
                {
                    this.code = TwoOperandsTranslator.ParseCMP(this.op1, this.op2, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "XCHG")
                {
                    this.code = TwoOperandsTranslator.ParseXCHG(this.op1, this.op2, ref this.needWriteMemory,
                                                                ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                                ref this.byteToWriteD8, ref this.needWriteD16,
                                                                ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "TEST")
                {
                    this.code = TwoOperandsTranslator.ParseTEST(this.op1, this.op2, ref this.needWriteMemory,
                                                                ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                                ref this.byteToWriteD8, ref this.needWriteD16,
                                                                ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "SHL")
                {
                    this.code = TwoOperandsTranslator.ParseSHL(this.op1, this.op2, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "SHR")
                {
                    this.code = TwoOperandsTranslator.ParseSHR(this.op1, this.op2, ref this.needWriteMemory,
                                                               ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                               ref this.byteToWriteD8, ref this.needWriteD16,
                                                               ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else if (this.cmdName == "IMUL")
                {
                    this.code = TwoOperandsTranslator.ParseIMUL(this.op1, this.op2, ref this.needWriteMemory,
                                                                ref this.byteIndToWriteMemory, ref this.needWriteD8,
                                                                ref this.byteToWriteD8, ref this.needWriteD16,
                                                                ref this.byteToWriteD16);
                    this.op1.SymType_ = SymTabElement.SymType.Pointer;
                    this.op2.SymType_ = SymTabElement.SymType.Pointer;
                }
                else
                {
                    throw new Exception("Unknown command!");
                }

                return;
            }

            #endregion

            throw new Exception("Invalid command!");
        }

        public UInt16 Size
        {
            get
            {
                return (UInt16) this.code.Length;
            }
        }

        public UInt16 Address
        {
            get
            {
                return this.address;
            }
        }

        private void SetMemoryName()
        {
            if (this.numOp == 0)
            {
                throw new Exception("Internal compiler error!");
            }
            if (this.numOp == 1)
            {
                if (this.op1.VariableName == null)
                {
                    throw new Exception("Internal compiler error!");
                }
                this.memoryName = this.op1.VariableName;
                this.memoryType = this.op1.SymType_;
                return;
            }
            if (this.numOp == 2)
            {
                if (this.op1.VariableName == null && this.op2.VariableName == null)
                {
                    throw new Exception("Internal compiler error!");
                }
                if (this.op1.VariableName != null && this.op2.VariableName != null)
                {
                    throw new Exception("Internal compiler error!");
                }
                if (this.op1.VariableName != null)
                {
                    this.memoryName = this.op1.VariableName;
                    this.memoryType = this.op1.SymType_;
                }
                if (this.op2.VariableName != null)
                {
                    this.memoryName = this.op2.VariableName;
                    this.memoryType = this.op2.SymType_;
                }
                return;
            }
            throw new Exception("Internal compiler error!");
        }

        public void SetMemoryAddress(TranslationResult res)
        {
            if (this.needWriteMemory)
            {
                this.SetMemoryName();
                if (!res.SymTabContains(this.memoryName, this.memoryType))
                {
                    throw new Exception("There is no such identifier with name " + this.memoryName + "!");
                }
                UInt16 adr = res.SymTabGetAddress(this.memoryName, this.memoryType);
                this.code[this.byteIndToWriteMemory] = (Byte) (adr & 255);
                this.code[this.byteIndToWriteMemory + 1] = (Byte) (adr >> 8);
            }
            if (this.needWriteD8)
            {
                this.SetMemoryName();
                if (!res.SymTabContains(this.memoryName, this.memoryType))
                {
                    throw new Exception("There is no such identifier with name " + this.memoryName + "!");
                }
                int adr = res.SymTabGetAddress(this.memoryName, this.memoryType);
                adr = adr - this.address - this.code.Length;
                if (adr < -128 || adr > 127)
                {
                    throw new Exception("Too big distance between commands!");
                }
                this.code[this.byteToWriteD8] = (Byte) (adr & 255);
            }
            if (this.needWriteD16)
            {
                this.SetMemoryName();
                if (!res.SymTabContains(this.memoryName, this.memoryType))
                {
                    throw new Exception("There is no such identifier with name " + this.memoryName + "!");
                }
                int adr = res.SymTabGetAddress(this.memoryName, this.memoryType);
                adr = adr - this.address - this.code.Length;
                if (adr < -32768 || adr > 32767)
                {
                    throw new Exception("Too big distance between commands!");
                }
                this.code[this.byteToWriteD16] = (Byte) (adr & 255);
                this.code[this.byteToWriteD16 + 1] = (Byte) (adr >> 8);
            }
        }

        public Byte GetByte(int ind)
        {
            return this.code[ind];
        }
    }
}